using System;
using System.Xml;
using System.Linq;
using System.Drawing;
using System.Collections.Generic;

namespace Clandestine.Engines.Field
{
	public class Layer
	{
		private Dictionary<int, Dictionary<int, Tile>> Scanlines = new Dictionary<int, Dictionary<int, Tile>>();
		private Dictionary<int, Dictionary<int, Tile>> TemporaryScanlines = new Dictionary<int, Dictionary<int, Tile>>();
		public LayerGroup ContainingGroup { get; set; } // group that contains this layer -- used to mark quads as dirty.
		public int Depth { get { return this.ContainingGroup.Layers.IndexOf(this); } } 
		public Point Position { get { return this.ContainingGroup.Position; } }
		public string Name { get; set; }
		public event TileChangedHandler TileChanged;
		public event VisibilityChangedHandler VisibilityChanged;
		
		// Used to count the number of times each spritesheet is used in the layer.
		// Need it so we can easily write <spritesheets> on Save()!
		public Dictionary<Spritesheet, int> SpritesheetRefCount = new Dictionary<Spritesheet, int>();
		
		private bool visible = true;
		public bool Visible
		{
			get { return visible; }
			set
			{
				if (value == visible)
					return;
				
				visible = value;
				
				if (this.VisibilityChanged != null)
					this.VisibilityChanged();
			}
		}
		
		public int MinY
		{
			get
			{
				lock (TemporaryScanlines)
	            	lock (Scanlines)
	            	{
	            		int min = 0;
	            		int minTemp = 0;
	                	if (Scanlines.Keys.Count != 0)
	                  	    min = Scanlines.Keys.Min();
	                  	if (TemporaryScanlines.Keys.Count != 0)
	                  		minTemp = TemporaryScanlines.Keys.Min();
	            		return (min < minTemp ? min : minTemp);
	            	}
           	}
        }

        public int MaxY
        {
			get
			{
				lock (TemporaryScanlines)
	            	lock (Scanlines)
	            	{
	            		int max = 0;
	            		int maxTemp = 0;
	                	if (Scanlines.Keys.Count != 0)
	                  	    max = Scanlines.Keys.Max();
	                  	if (TemporaryScanlines.Keys.Count != 0)
	                  		maxTemp = TemporaryScanlines.Keys.Max();
	            		return (max > maxTemp ? max : maxTemp);
	            	}
			}
        }

        public int MinX
        {
        	get
        	{
        		lock (TemporaryScanlines)
		            lock (Scanlines)
		            {
		                int minx = int.MaxValue;
		                foreach (Dictionary<int, Tile> dis in Scanlines.Values)
		                {
		                    if (dis.Count == 0)
		                        continue;
		                    int a = dis.Keys.Min();
		                    if (a < minx)
		                        minx = a;
		                }
		              	foreach (Dictionary<int, Tile> dis in TemporaryScanlines.Values)
		                {
		                    if (dis.Count == 0)
		                        continue;
		                    int a = dis.Keys.Min();
		                    if (a < minx)
		                        minx = a;
		                }
		                return minx;
		            }
            }
        }

        public int MaxX
        {
        	get
        	{
        		lock (TemporaryScanlines)
		            lock (Scanlines)
		            {
		                int maxx = int.MinValue;
		                foreach (Dictionary<int, Tile> dis in Scanlines.Values)
		                {
		                    if (dis.Count == 0)
		                        continue;
		                    int a = dis.Keys.Max();
		                    if (a > maxx)
		                        maxx = a;
		                }
		                foreach (Dictionary<int, Tile> dis in TemporaryScanlines.Values)
		                {
		                    if (dis.Count == 0)
		                        continue;
		                    int a = dis.Keys.Max();
		                    if (a > maxx)
		                        maxx = a;
		                }
		                return maxx;
		            }
            }
        }
		
		public Layer(LayerGroup parent)
		{
			this.ContainingGroup = parent;
		}
		
		public Tile? GetTile(int x, int y)
		{
			lock (Scanlines)
			{
				if (Scanlines.ContainsKey(y))
					if (Scanlines[y].ContainsKey(x))
						return Scanlines[y][x];
				return null;
			}
		}
		
		public Tile? GetTemporaryTile(int x, int y)
		{
			lock (TemporaryScanlines)
			{
				if (TemporaryScanlines.ContainsKey(y))
					if (TemporaryScanlines[y].ContainsKey(x))
						return TemporaryScanlines[y][x];
				return null;
			}
		}
		
		public Tile? AddTile(Tile? tile, int x, int y) 
		{
			// Let the caller pass in a null tile -- treat as erase!
			if (tile == null)
				return this.ClearTile(x, y);
		
			Tile? oldTile = null; // the tile that used to be there -- return it in case the painter wants to remember (you know, do undo() or something >.>)
			lock (Scanlines)
			{
				// Does that scanline exist?
				if (!Scanlines.ContainsKey(y))
					Scanlines.Add(y, new Dictionary<int, Tile>());
					
				// Already got a tile there?
				if (Scanlines[y].ContainsKey(x))
				{
					oldTile = Scanlines[y][x];
					Scanlines[y][x] = tile.Value;
				}
				else
					Scanlines[y].Add(x, tile.Value);
					
				// Spritesheet ref counting.
				if (SpritesheetRefCount.ContainsKey(tile.Value.Spritesheet))
					SpritesheetRefCount[tile.Value.Spritesheet]++;
				else
					SpritesheetRefCount.Add(tile.Value.Spritesheet, 1);
			}
			
			if (TileChanged != null)
				TileChanged(this.Depth, x, y);
			return oldTile;
		}
		
		public void AddTemporaryTile(Tile tile, int x, int y)
		{
			lock (TemporaryScanlines)
			{
				// Does that scanline exist?
				if (!TemporaryScanlines.ContainsKey(y))
					TemporaryScanlines.Add(y, new Dictionary<int, Tile>());
					
				if (TemporaryScanlines[y].ContainsKey(x))
					TemporaryScanlines[y][x] = tile;
				else
					TemporaryScanlines[y].Add(x, tile);
			}
			
			if (TileChanged != null)
				TileChanged(this.Depth, x, y);
		}
		
		public Tile? ClearTile(int x, int y) 
		{
			Tile? oldTile = null; // in-case the caller wants to implement undo.
			lock (Scanlines)
			{
				// Does that scanline exist?
				if (!Scanlines.ContainsKey(y))
					return null;
					
				// Do we have a tile there?!
				if (Scanlines[y].ContainsKey(x))
				{
					// Spritesheet ref counting.
					if (SpritesheetRefCount.ContainsKey(Scanlines[y][x].Spritesheet))
						SpritesheetRefCount[Scanlines[y][x].Spritesheet]--;
					else
						Clandestine.Base.Log.w("Spritesheet ref-counting seems to be broken.");
				
					// Erase
					oldTile = Scanlines[y][x];
					Scanlines[y].Remove(x);
				}
				else
					return null;
			}
			
			if (TileChanged != null)
				TileChanged(this.Depth, x, y);
			return oldTile;
		}
		
		public void ClearTemporaryTiles()
		{
			lock (TemporaryScanlines)
			{
				// gotta remember old termporary tiles so we can fire TileChanged's!
				Dictionary<int, Dictionary<int, Tile>> oldTemp = this.TemporaryScanlines;
				
				// Clear!
				this.TemporaryScanlines = new Dictionary<int, Dictionary<int, Tile>>();

				// Fire TileChangeds
				if (TileChanged != null)
					foreach (KeyValuePair<int, Dictionary<int, Tile>> kvpY in oldTemp)
						foreach (KeyValuePair<int, Tile> kvpX in kvpY.Value)
							TileChanged(this.Depth, kvpY.Key, kvpX.Key);
			}
		}
		
		public void Save(XmlWriter xml)
		{
			xml.WriteStartElement("layer");
			xml.WriteAttributeString("depth", this.Depth.ToString());
			
			// Use our minx and miny as offsets so we dont have to save loads of empty lines/tiles!
			xml.WriteAttributeString("offset-x", this.MinX.ToString());
			xml.WriteAttributeString("offset-y", this.MinY.ToString());
			
			// Layer datas!
			string layerData = string.Empty;
			for (int y = this.MinY; y <= this.MaxY; y++)
			{
				if (this.Scanlines.ContainsKey(y))
				{
					for (int x = this.MinX; x <= this.MaxX; x++)
						if (this.Scanlines[y].ContainsKey(x))
							layerData += String.Format("{0}.{1} ", Scanlines[y][x].Spritesheet.Number, Scanlines[y][x].TileNumber);
					// last space doesn't matter
				}
				layerData += "\r\n"; // last new line doesn't matter
			}
			
			xml.WriteCData(layerData); // annnd done.
			
			xml.WriteEndElement();
		}
		
		public void Load(XmlReader xml, Dictionary<int, int> remapping)
		{
			if ((xml.NodeType != XmlNodeType.Element) || (xml.Name != "layer"))
				throw new Exception(":(");
		
			// Position is what the layer treats as (0, 0)
			// Offset is just used in loading and means the offset in the layer that layer data is written as in XML!
		
			// Get attributes
			Point offset = new Point();
			offset.X = int.Parse(xml.GetAttribute("offset-x"));
            offset.Y = int.Parse(xml.GetAttribute("offset-y"));
            Name = xml.GetAttribute("name");
			// ignore depth attribute. That's just to make the xml read-able. Layers loaded in order, smallest depth first.
			
			// Get data
			while (xml.NodeType != XmlNodeType.CDATA)
				xml.Read();
			string data = xml.Value;
			string[] lines = data.Split("\r\n".ToCharArray());
			
			// Parse the data... oh god.
			lock (Scanlines)
				for (int y = 0; y < lines.Length; y++)
				{
					if (lines[y] == string.Empty)
						continue;
					
					string[] tiles = lines[y].Split(" ".ToCharArray());
					
					for (int x = 0; x < tiles.Length; x++)
					{
						if ((tiles[x] == "-") || (tiles[x] == string.Empty)) // no tile here
							continue;
									
						string[] tile = tiles[x].Split(".".ToCharArray());

						int spritesheetNum = int.Parse(tile[0]); // ignore for now
						int tileNum = int.Parse(tile[1]); 
						
						// remap the spritesheetNum?
						// used if the number the spritesheet was loaded at is different to the one this file uses!
						if (remapping.ContainsKey(spritesheetNum))
							spritesheetNum = remapping[spritesheetNum];
						
						Tile t = new Tile(this.ContainingGroup.Map.Spritesheets[spritesheetNum], tileNum);
						this.AddTile(t, x + offset.X, y + offset.Y);
					}
				}
		}
	}
}

